package app;


import java.util.*;

enum ImpurityMeasure {
	Gini, Entropy
};

public class DataSet {
	private AttributeSet attributeSet;
	private SymbolicAttribute problemClass;
	private Vector<Instance> instanceSet;

	public DataSet() { }

	public DataSet(AttributeSet attributeSet) {
		if (attributeSet == null)
			throw new IllegalArgumentException("Invalid argument");

		this.attributeSet = attributeSet;
		// TODO: test if the problem class is an SymbolicAttribute
		this.problemClass = (SymbolicAttribute) attributeSet.get(attributeSet.size() - 1);

		instanceSet = new Vector<Instance>();
	}
	
	private double calculateDistance(Instance instance, Instance newInstance) {
//		System.out.println("\nInstance:" + instance + "new instance: " + newInstance);
		List<AttributeValue> values = instance.getValues();
		List<AttributeValue> newValues = newInstance.getValues();
		double[] distances = new double[values.size() - 1];
		double euclideanDistance = 0;
		double sum = 0;

		for (int i = 0; i < values.size() - 1; i++) {
			AttributeValue value = values.get(i);
			AttributeValue newValue = newValues.get(i);

			// calculate distances for symbolic values
			if (value instanceof SymbolicValue) {
				if (value.equals(newValue)) {
					distances[i] = 0;
				} else {
					distances[i] = 1;
				}
			}

			// calculate distances for numeric values
			else if (value instanceof NumericValue) {
				NumericAttribute numAttribute = (NumericAttribute) attributeSet.get(i);

				// we normalize the values for both the new and old instance
				double doubleValue = ((NumericValue) value).getValue();
				double doubleNewValue = ((NumericValue) newValue).getValue();
				double minValue = numAttribute.getMinValue().getValue();
				double maxValue = numAttribute.getMaxValue().getValue();
				double normalizedValue = (doubleValue - minValue) / (maxValue - minValue);
				double normalizedNewValue = (doubleNewValue - minValue) / (maxValue - minValue);

				distances[i] = Math.abs(normalizedNewValue - normalizedValue);
			}
			sum += Math.pow(distances[i], 2);
			// System.out.println("Value: " + value + " new Value: " + newValue
			// + " distance: " +distances[i]);
			// System.out.println("Sum = " + sum);
		}
		euclideanDistance = Math.sqrt(sum);
		// System.out.println("Euclidean distance between instances " + instance
		// + " and " + newInstance + ": " + euclideanDistance );
		return euclideanDistance;
	}

	// TODO: see when the result is 'Infinity'
	private double[] calculateWeights(List<Double> remainingDistances) {
		int size = remainingDistances.size();
		double[] weights = new double[size];
		for (int i = 0; i < size; i++) {
			weights[i] = 1 / Math.pow((remainingDistances.get(i)), 2);
			System.out.println("distance: " + remainingDistances.get(i) + " w = " + weights[i]);
		}

		return weights;
	}

	// TODO: if distance = 0 then class(newInstance) = class(instance)
	public void kNearestNeighbour(final Vector<Instance> instances, Instance newInstance, int k, boolean flag) {
		final List<Double> euclideanDistances = new ArrayList<Double>();
		List<Double> remainingDistances = new ArrayList<Double>();
		Vector<Instance> remainingInstances = new Vector<Instance>();
		SymbolicValue newClassValue = null;

		// calculate distances between the new instance and the other instances
		for (Instance instance : instances) {
			double distance = calculateDistance(instance, newInstance);
			euclideanDistances.add(distance);
		}

		// sort instances according to distances
		Collections.sort(instances, new Comparator<Instance>() {

			public int compare(Instance instance1, Instance instance2) {
				int index1 = instances.indexOf(instance1);
				int index2 = instances.indexOf(instance2);
				if (euclideanDistances.get(index1) > euclideanDistances.get(index2))
					return 1;
				return 0;
			}
		});

//		System.out.println(instances);

		Collections.sort(euclideanDistances);

		if (flag == true) {
			// get the k nearest neighbors with the k smallest distances
			for (int i = 0; i < k; i++) {
				remainingDistances.add(euclideanDistances.get(i));
				remainingInstances.add(instances.get(i));
			}
			System.out.println("Cele mai mici k distante: ");
			System.out.println(remainingDistances);
			System.out.println("Cele mai apropiate k instante: ");
			System.out.println(remainingInstances);

			// calculate weights for k smallest distances
			double[] weights = calculateWeights(remainingDistances);

			/* classify the new instance - find out the class value for it */

			// first way - calculate sums
			double[] sums = new double[getProblemClass().noValues()];
			for (int i = 0; i < remainingInstances.size(); i++) {
				Instance remInstance = remainingInstances.get(i);
				SymbolicValue classValue = (SymbolicValue) remInstance.getValue(remInstance.noAttributes() - 1);
				int intClassValue = classValue.hashCode();
				sums[intClassValue] += weights[i];
			}
			double maxSum = Double.MIN_VALUE;
			int indexForMax = 0;
			for (int i = 0; i < sums.length; i++) {
				if (sums[i] > maxSum) {
					maxSum = sums[i];
					indexForMax = i;
				}
			}
			newClassValue = getProblemClass().getValue(indexForMax);
			System.out.println("Clasa pentru noua instanta: " + newClassValue);
		} else if (flag == false) {

			// second way - choose minimum distance
			double minDistance = euclideanDistances.get(0);
			Instance minDistanceInstance = instances.get(euclideanDistances.indexOf(minDistance));
			newClassValue = (SymbolicValue) minDistanceInstance.getValue(minDistanceInstance.getValues().size() - 1);
			System.out.println("Clasa pentru noua instanta: " + newClassValue);
		}
	}

	public void add(Instance instance) {
		if (instance == null || instance.noAttributes() != attributeSet.size())
			throw new IllegalArgumentException("Invalid instance");

		instanceSet.add(instance);
	}

	public AttributeSet getAttributeSet() {
		return this.attributeSet;
	}
	
	public Vector<Instance> getInstanceSet() {
		return instanceSet;
	}
	
	public SymbolicAttribute getProblemClass() {
		return problemClass;
	}

	public boolean isEmpty() {
		return instanceSet.size() <= 0;
	}

	public String toString() {
		String str = "";

		str += attributeSet + "\n";

		str += "Instances: \n";
		for (Instance instance : instanceSet) {
			str += instance;
		}

		return str;
	}
}
